#include <ani.h>
#include <array>
#include <cstring>
#include <iostream>

#include "ani_helpers_build_scripts.h"

ani_int impl_ManagedStringWrite([[maybe_unused]] ani_env *env, [[maybe_unused]] ani_class aniClas, ani_string strAni,
                                ani_long buffer, ani_int offset) {
  // Notice that we use UTF length for buffer size, but counter is expressed in number of Unicode chars.
  ani_size lengthUtf8 = 0;
  env->String_GetUTF8Size(strAni, &lengthUtf8);

  std::string str(lengthUtf8 + 1, 0);

  ani_size lengthUtf16 = 0;
  env->String_GetUTF16Size(strAni, &lengthUtf16);
  ani_size count = 0;
  env->String_GetUTF8SubString(strAni, 0, lengthUtf16, str.data(), lengthUtf8 + 1, &count);

  std::memcpy(reinterpret_cast<char *>(buffer) + offset, str.data(), str.length());

  return str.length();
}

static ani_string impl_Utf8ToString([[maybe_unused]] ani_env *env, [[maybe_unused]] ani_class aniClas, ani_long data,
                                    ani_int offset, ani_int length) {
  const char *strBuf = reinterpret_cast<const char *>(data) + offset;
  ani_string result{};
  CHECK_ANI_FATAL(env->String_NewUTF8(strBuf, length - 1 /* drop zero terminator */, &result));
  return result;
}

static ani_long impl_Malloc(ani_long size) { return reinterpret_cast<ani_long>(malloc(size)); }

static void impl_Free(ani_long ptr) {
  return free(reinterpret_cast<char *>(ptr));
  ;
}

static ani_int impl_ReadByte(ani_long data, ani_long index, ani_long length) {
  char *src = reinterpret_cast<char *>(data) + index;
  return *reinterpret_cast<ani_int *>(src);
}

static void impl_WriteByte(ani_long data, ani_long index, ani_long length, ani_int value) {
  char *dst = reinterpret_cast<char *>(data) + index;
  std::memcpy(dst, &value, 1);
}

ANI_EXPORT ani_status ANI_Constructor(ani_vm *vm, uint32_t *result) {
  ani_env *env{};
  CHECK_ANI_FATAL(vm->GetEnv(ANI_VERSION_1, &env));

  constexpr const char *className = "InteropNativeModule.InteropNativeModule";
  ani_class cls;
  CHECK_ANI_FATAL(env->FindClass(className, &cls));

  std::array methods = {
      ani_native_function{"_WriteByte", "llli:", reinterpret_cast<void *>(impl_WriteByte)},
      ani_native_function{"_ReadByte", "lll:i", reinterpret_cast<void *>(impl_ReadByte)},
      ani_native_function{"_Free", "l:", reinterpret_cast<void *>(impl_Free)},
      ani_native_function{"_Malloc", "l:l", reinterpret_cast<void *>(impl_Malloc)},
      ani_native_function{"_Utf8ToString", "lii:C{std.core.String}", reinterpret_cast<void *>(impl_Utf8ToString)},
      ani_native_function{"_ManagedStringWrite", "C{std.core.String}li:i",
                          reinterpret_cast<void *>(impl_ManagedStringWrite)},
  };

  CHECK_ANI_FATAL(env->Class_BindStaticNativeMethods(cls, methods.data(), methods.size()));

  *result = ANI_VERSION_1;
  return ANI_OK;
}
